// Copyright (c) 2022 by Duguang.IO Inc. All Rights Reserved.
// Author: deng hualin
// Date: 2022-07-05

package engine

import (
	"context"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"io"
	api "k8s.io/api/core/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

// GetPodStatus returns true if the pod is running
//
// 返回有err表示pod已经到了最终状态,没有err表示pod的状态还会改变
// 最终状态是running或者failed
func GetPodStatus(ctx context.Context, k kubernetes.Interface, podName, namespace string, out io.Writer, es map[string]*v1.Event) (running bool, err error, phase api.PodPhase) {
	events, err := k.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{
		FieldSelector: fmt.Sprintf("involvedObject.name=%s", podName),
	})

	if err != nil {
		logrus.WithError(err).Error("cannot get the pod events")
	} else {
		for _, e := range events.Items {
			if es[e.Message] == nil {
				es[e.Message] = &e
				logrus.WithField("event", e.Message).Debug("pod event")
				out.Write([]byte(fmt.Sprintf("## pod %s/%s 事件: %v\n", namespace, podName, e.Message)))
			}

		}
	}

	pod, err := k.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})

	// TODO 获取pod的状态失败,很难玩,不知道POD是什么情况,是执行成功了,还是执行中等等,这处理成POD done,失败掉
	if err != nil {
		logrus.WithError(err).
			Errorln("cannot get the pod status")
		return false, errors.New("cannot get the pod status"), pod.Status.Phase
	}

	logrus.WithField("pod", pod.Status.Phase).
		WithField("podName", podName).
		Debugln("successfully get the pod")

	// check status of containers
	// 处理一些已知情况
	for _, container := range append(pod.Status.ContainerStatuses, pod.Status.InitContainerStatuses...) {
		if container.Ready {
			continue
		}
		waiting := container.State.Waiting
		if waiting == nil {
			continue
		}

		switch waiting.Reason {
		case "InvalidImageName":
			out.Write([]byte(fmt.Sprintf("## pod %s/%s 容器 %s image invalid,error message: [%s] reason [%s]\n", namespace, podName, container.Name, waiting.Message, waiting.Reason)))
			return false, fmt.Errorf("image pull failed: %s", waiting.Message), pod.Status.Phase
		case "ErrImagePull", "ImagePullBackOff":
			out.Write([]byte(fmt.Sprintf("## pod %s/%s %s 出现错误,error message: [%s] reason [%s]\n", namespace, podName, container.Name, waiting.Message, waiting.Reason)))
			msg := fmt.Sprintf("image %s pull failed: %s", waiting.Message, container.Image)
			return false, errors.New(msg), pod.Status.Phase
		}
	}

	// 其他情况
	run, err := isRunning(pod)
	return run, err, pod.Status.Phase
}
func (k *Kubernetes) GetContainerStatus(ctx context.Context, ContainerName, podName, namespace string) (running bool, err error, exitCode int32) {
	return GetContainerStatus(ctx, k.client, ContainerName, podName, namespace)
}

func GetContainerStatus(ctx context.Context, k kubernetes.Interface, ContainerName, podName, namespace string) (running bool, err error, exitCode int32) {
	pod, err := k.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})

	// TODO 获取pod的状态失败,很难玩,不知道POD是什么情况,是执行成功了,还是执行中等等,这处理成POD done,失败掉
	if err != nil {
		logrus.WithError(err).
			Errorln("cannot get the pod status")
		return false, errors.New("cannot get the pod status"), -1
	}

	logrus.WithField("pod", pod.Status.Phase).
		WithField("podName", podName).
		Debugln("successfully get the pod")

	// check status of containers
	// 处理一些已知情况
	var exitcode int32 = -1
	for _, container := range append(pod.Status.ContainerStatuses, pod.Status.InitContainerStatuses...) {
		if container.Name != ContainerName {
			continue
		}

		if container.State.Terminated != nil {
			exitcode = container.State.Terminated.ExitCode
			if container.State.Terminated.Reason == "Completed" {
				return false, nil, exitcode
			}
			return false, errors.New(container.State.Terminated.Reason), exitcode
		}

		if container.Ready {
			continue
		}
		waiting := container.State.Waiting
		if waiting == nil {
			continue
		}

		switch waiting.Reason {
		case "InvalidImageName":
			return false, fmt.Errorf("image pull failed: %s", waiting.Message), exitcode
		case "ErrImagePull", "ImagePullBackOff":
			msg := fmt.Sprintf("image %s pull failed: %s", waiting.Message, container.Image)
			return false, errors.New(msg), exitcode
		}
	}

	// 其他情况
	return true, nil, exitcode
}

// isRunning returns true if the pod is running
// 返回有err表示pod已经到了最终状态,没有err表示pod的状态还会改变
func isRunning(pod *api.Pod) (running bool, err error) {
	switch pod.Status.Phase {
	case api.PodRunning:
		return true, nil
	case api.PodSucceeded:
		return false, fmt.Errorf("pod已经完毕且已经执行成功")
	case api.PodFailed:
		return false, fmt.Errorf("pod status is failed")
	default:
		return false, nil
	}
}
